using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Controls;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace WIAssistant
{
    public class MoveWorkItem
    {

        public MoveWorkItem()
        {
            ErrorReports = new List<ErrorReport>();
        }

        // An event to expose to others when we are about to save.
        public event BeforeMovedWorkItemSave OnBeforeMovedWorkItemSave;

        public List<ErrorReport> ErrorReports { get; set; }

        public void MoveAllWorkItems(ObservableCollection<WorkItemTypeMapping> mappings, Dictionary<WorkItem, Canvas> pad, IterationNode selectedIterationNode, IMain formUI, BackgroundWorker worker)
        {
            // Get the list of final states of moved items for all the projects.  This also contains transistions on how to get to any states needed.
            List<StateTransitionInfo> allStateTransInfo =
                MiscHelpers.DeserializeObject<List<StateTransitionInfo>>("StateTransitionInfo.xml");

            // iterate through all the mappings that we have made (ie each source project/type mapped to a dest project/type)
            foreach (WorkItemTypeMapping mapping in mappings)
            {
                // Prevent Closure issue with linq
                WorkItemTypeMapping typeMapping = mapping;

                // Get the state transition info for the current mapping source project
                StateTransitionInfo currentStateTransInfo =
                    allStateTransInfo.Find(stateTrans => stateTrans.ProjectID == typeMapping.SourceProject.Id);
                // If there are no transitions for this project then load the default
                if (currentStateTransInfo == null)
                {
                    currentStateTransInfo = allStateTransInfo.Find(stateTrans => stateTrans.IsDefaultEntry);
                }

                // Get all the work items for the current mapping.  The ToList is significat here
                // by doing that we effectivly make a second list.  If we did not do that then when we iterate
                // below and then remove items from the Pad we would get an exception.
                var workItems = pad.Keys.Where(wi => IsMatchingProjectAndType(wi, typeMapping)).ToList();
                
                string commentPrefix;

                // Iterate through all the work items and update the iteration path for each
                foreach (WorkItem sourceWorkItem in workItems)
                {
                    // Save off the source state so we can try to get the copied item to that state.
                    String sourceState = sourceWorkItem.State;
                    // Save off the source project so we know that for commenting
                    String project = sourceWorkItem.Project.Name;

                    // Report Progress of what Work item we are moveing
                    worker.ReportProgress(0, sourceWorkItem);

                    // This is just a pointer to the work item we will save (it will be new if we
                    // are moving accross projects or types.  Otherwise it will the the item we are iterating with.)
                    WorkItem workItemToSave;

                    // Check to see if we only need to change the iteration path
                    //  This is for internal project moves on the same type.
                    bool iterationIsOnlyChangeNeeded = IterationIsOnlyChangeNeeded(mapping);
                    if (iterationIsOnlyChangeNeeded)
                    {
                        // We set the iteration down below.  We just need to indicate that our
                        // source and dest are the same thing.  We do that by setting the "To Save" item to be the source Item.
                        workItemToSave = sourceWorkItem;

                    }
                        // This is the move that is really a copy.  We will make a new workitem
                        //  and then do I best to copy this one into that one.
                    else
                    {

                        // Load the list of items not to copy from a file
                        List<String> fieldsToNotCopy = new List<String>();
                        fieldsToNotCopy.DeserializeStringList("FieldsToNotMove.xml");

                        // This will populate the new work item as best it can.  Our mappings will
                        // overwrite with the more specific data mapping that the user setup.
                        WorkItem copyWI = sourceWorkItem.Copy(mapping.DestType);
                        // Set the comment of the copy link (always the last one).
                        copyWI.Links[copyWI.Links.Count - 1].Comment = "WIAssistant: Moved Work Item";
                        // Save the copied workitem
                        copyWI.Save();

                        // Iterate through the mappings for this (source and dest) (project/type).
                        foreach (var destFieldMapping in mapping.DestFieldMappings)
                        {
                            // Only try to change those we can change
                            if (copyWI.Fields[destFieldMapping.Key.Name].IsEditable != true)
                                continue;

                            // Don't try to copy location specific fields (ie Iteration Stuff)
                            if (fieldsToNotCopy.Contains(destFieldMapping.Key.Name))
                                continue;

                            try
                            {
                                // Copy the source value into the dest value
                                copyWI.Fields[destFieldMapping.Key.Name].Value =
                                    sourceWorkItem.Fields[destFieldMapping.Value.Name].Value;

                                // See if this is a valid copy.
                                if (copyWI.Fields[destFieldMapping.Key.Name].Status != FieldStatus.Valid)
                                {
                                    // If the copy was not valid then revert to the orginal value
                                    copyWI.Fields[destFieldMapping.Key.Name].Value =
                                        copyWI.Fields[destFieldMapping.Key.Name].OriginalValue;
                                }

                            }
                            catch (Exception e)
                            {
                                ErrorReport errorReport = new ErrorReport
                                                              {
                                                                  DestFieldDef = destFieldMapping.Key,
                                                                  SourceFieldDef = destFieldMapping.Value,
                                                                  InternalErrorMessage = e.Message,
                                                                  WorkItem = sourceWorkItem
                                                              };
                                ErrorReports.Add(errorReport);
                            }


                        }

                        
                        commentPrefix = "WIAssistant: Work Item Moved to: " + selectedIterationNode + ".";
                        SetWorkItemToState(currentStateTransInfo.MovedState, sourceWorkItem, commentPrefix);

                        workItemToSave = copyWI;
                    }


                    // Set the iteration path to the selected one
                    workItemToSave.Fields["Iteration Path"].Value = selectedIterationNode.IterationPath;

                    // If someone wants to know we are about to save then tell them
                    if (OnBeforeMovedWorkItemSave != null)
                        OnBeforeMovedWorkItemSave(sourceWorkItem, mapping);

                    // Save the changes to the workitem (or create it as the case may be).
                    workItemToSave.TryOpen();
                    workItemToSave.Save();

                    // Try to get to the the state that the orginal was. 
                    // Only do this if we made a new work item.
                    if (iterationIsOnlyChangeNeeded == false)
                    {
                        commentPrefix = "WIAssistant: Work Item Moved from: " + project + ".";
                        SetWorkItemToState(sourceState, workItemToSave, commentPrefix);    
                    }
                    
                    // Remove the workitem from the pad.
                    WorkItem sourceWorkItemClosure = sourceWorkItem;
                    MainMediator.Instance.Dispatcher.Invoke(
                        new Action(() => pad.RemoveWorkItemFromPad(sourceWorkItemClosure, formUI)));
                }
            }

            // Clean up and be ready for the next run
            //_vm.ClearSelectedMoveIteration();
            //_vm.ClearProject();
        }

        private void SetWorkItemToState(string state, WorkItem workItem, string commentPrefix)
        {
            // Set the sourceWorkItem's state so that it is clear that it has been moved.
            string originalState = (string)workItem.Fields["State"].Value;

            // Try to set the state of the source work item to the "Deleted/Moved" state (whatever is defined in the file.
            
            // We need an open work item to set the state
            workItem.TryOpen();

            // See if we can go directly to the planned state.
            workItem.Fields["State"].Value = state;


            if (workItem.Fields["State"].Status != FieldStatus.Valid)
            {
                // Revert back to the orginal value and start searching for a way to our "MovedState"
                workItem.Fields["State"].Value = workItem.Fields["State"].OriginalValue;

                // If we can't then try to go from the current state to another state.  Saving each time till we get to where we are going.
                foreach (string curState in workItem.Type.FindNextState((string)workItem.Fields["State"].Value, state))
                {
                    string comment;
                    if (curState == state)
                        comment = commentPrefix + Environment.NewLine + "  State changed to " + state;
                    else
                        comment = commentPrefix + Environment.NewLine + "  State changed to " + curState + " as part of move toward a state of " + state;

                    bool success = ChangeWorkItemState(workItem, originalState, curState, comment);
                    // If we could not do the incrental state change then we are done.  We will have to go back to the orginal...
                    if (!success)
                        break;
                }
            }
            else
            {
                // Just save it off if we can.
                string comment = commentPrefix + "\n   State changed to " + state;
                ChangeWorkItemState(workItem, originalState, state, comment);

            }
        }
        private bool ChangeWorkItemState(WorkItem workItem, string orginalSourceState, string destState, String comment)
        {
            // Try to save the new state.  If that fails then we also go back to the orginal state.
            try
            {
                workItem.TryOpen();
                workItem.Fields["State"].Value = destState;
                workItem.History = comment;
                workItem.Save();
                return true;
            }
            catch (Exception e)
            {
                ErrorReport errorReport = new ErrorReport
                                              {
                                                  DestFieldDef = null,
                                                  SourceFieldDef = null,
                                                  ExternalErrorMessage = "The State " + destState + " could not be transitioned to from " + workItem.Fields["State"].OriginalValue,
                                                  InternalErrorMessage = e.Message,
                                                  WorkItem = workItem
                                              };
                ErrorReports.Add(errorReport);

                // Revert back to the original value.
                workItem.Fields["State"].Value = orginalSourceState;
                return false;
            }
        }

        /// <summary>
        /// Find out if the work item passed in has the same project and type 
        /// as the source of the mapping that was passed in.
        /// </summary>
        /// <param name="workItem"></param>
        /// <param name="mapping"></param>
        /// <returns></returns>
        private bool IsMatchingProjectAndType(WorkItem workItem, WorkItemTypeMapping mapping)
        {
            bool projectsAreTheSame = false;
            if (workItem.Project.Id == mapping.SourceProject.Id)
                projectsAreTheSame = true;


            bool typesAreTheSame = false;
            if (workItem.Type.Name == mapping.SourceType.Name)
                typesAreTheSame = true;

            return projectsAreTheSame && typesAreTheSame;
        }

        /// <summary>
        /// Find out if this mapping only needs an iteration change.  This is true when 
        /// the source and dest projects and types are exactly the same.
        /// </summary>
        /// <param name="mapping"></param>
        /// <returns></returns>
        private bool IterationIsOnlyChangeNeeded(WorkItemTypeMapping mapping)
        {
            bool projectsAreTheSame = false;
            if (mapping.SourceProject.Id == mapping.DestProject.Id)
                projectsAreTheSame = true;

            bool typesAreTheSame = false;
            if (mapping.SourceType.Name == mapping.DestType.Name)
                typesAreTheSame = true;

            return projectsAreTheSame && typesAreTheSame;
        }
    }
}